package com.linking.mongo.manager;

import cn.hutool.core.util.StrUtil;
import com.borealis.common.utils.DataUtils;
import com.borealis.common.utils.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

/**
 * @Author YaoWeiXin
 * @Date 2020/4/14 14:43
 * @Description mongo工具虚类
 */
@Slf4j
public abstract class AbstractMongoUtils {

  @Resource
  AbstractMongoManager mongoManager;

  /**
   * 根据键值对查找对象
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKeys(String db, String collection, Class<T> clz,
      Map<String, Object> params) {
    Query query = new Query();
    for (Entry<String, Object> set : params.entrySet()) {
      query.addCriteria(analyzeQueryParam(set.getKey(), set.getValue()));
    }
    if (StringUtils.isEmpty(collection)) {
      return Optional.ofNullable(mongoManager.getTemplate(db).findOne(query, clz));
    } else {
      return Optional.ofNullable(mongoManager.getTemplate(db).findOne(query, clz, collection));
    }
  }

  /**
   * 根据键值查找对象
   *
   * @param clz   对象的Class
   * @param key   字段
   * @param value 值
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKey(Class<T> clz, String key, String value) {
    return findByKeys(null, null, clz, DataUtils.parseMap(key, value));
  }

  /**
   * 根据键值查找对象
   *
   * @param db    库名
   * @param clz   对象的Class
   * @param key   字段
   * @param value 值
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKey(String db, Class<T> clz, String key, String value) {
    return findByKeys(db, null, clz, DataUtils.parseMap(key, value));
  }

  /**
   * 根据键值查找对象
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param key        字段
   * @param value      值
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKey(String db, String collection, Class<T> clz, String key,
      String value) {
    return findByKeys(db, collection, clz, DataUtils.parseMap(key, value));
  }

  /**
   * 根据键值对查找对象
   *
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKeys(Class<T> clz, Map<String, Object> params) {
    return findByKeys(null, null, clz, params);
  }

  /**
   * 根据键值对查找对象
   *
   * @param db     库名
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> Optional<T> findByKeys(String db, Class<T> clz, Map<String, Object> params) {
    return findByKeys(db, null, clz, params);
  }


  /**
   * 根据键值对查找对象并修改
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @param update     更新参数
   * @return 查出来的对象
   */
  public <T> T findAndModify(String db, String collection, Class<T> clz,
      Map<String, Object> params, Update update) {
    return findAndModify(db, collection, clz, params, update, true);
  }

  /**
   * 根据键值对查找对象并修改
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @param update     更新参数
   * @param upInsert   是否插入
   * @return 查出来的对象
   */
  public <T> T findAndModify(String db, String collection, Class<T> clz,
      Map<String, Object> params, Update update, Boolean upInsert) {
    Query query = new Query();
    for (Entry<String, Object> set : params.entrySet()) {
      query.addCriteria(analyzeQueryParam(set.getKey(), set.getValue()));
    }
    FindAndModifyOptions opt = new FindAndModifyOptions();
    opt.upsert(upInsert);
    opt.returnNew(true);
    if (StringUtils.isEmpty(collection)) {
      return mongoManager.getTemplate(db).findAndModify(query, update, opt, clz);
    } else {
      return mongoManager.getTemplate(db).findAndModify(query, update, opt, clz, collection);
    }
  }

  /**
   * 根据键值对查找对象并修改
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @param update     更新参数
   */
  public <T> void updateMulti(String db, String collection, Class<T> clz,
      Map<String, Object> params, Update update) {
    Query query = new Query();
    for (Entry<String, Object> set : params.entrySet()) {
      query.addCriteria(analyzeQueryParam(set.getKey(), set.getValue()));
    }
    if (StringUtils.isEmpty(collection)) {
      mongoManager.getTemplate(db).updateMulti(query, update, clz);
    } else {
      mongoManager.getTemplate(db).updateMulti(query, update, clz, collection);
    }
  }

  /**
   * 根据键值对查找对象集合
   *
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> List<T> findList(Class<T> clz, Map<String, Object> params) {
    return findList(null, null, clz, params, 0L, 0L, null);
  }

  /**
   * 根据键值对查找对象集合
   *
   * @param db     库名
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> List<T> findList(String db, Class<T> clz, Map<String, Object> params) {
    return findList(db, null, clz, params, 0L, 0L, null);
  }

  /**
   * 根据键值对查找对象集合
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @return 查出来的对象
   */
  public <T> List<T> findList(String db, String collection, Class<T> clz,
      Map<String, Object> params) {
    return findList(db, collection, clz, params, 0L, 0L, null);
  }

  /**
   * 根据键值对查找对象集合
   *
   * @param clz      对象的Class
   * @param params   参数
   * @param page     分页页数
   * @param pageSize 分页每页数量
   * @param sortWith 排序
   * @return 查出来的对象
   */
  public <T> List<T> findList(Class<T> clz, Map<String, Object> params, Long page, Long pageSize,
      String sortWith) {
    return findList(null, null, clz, params, page, pageSize, sortWith);
  }

  /**
   * 根据键值对查找对象集合
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @param page       分页页数
   * @param pageSize   分页每页数量
   * @param sortWith   排序
   * @return 查出来的对象
   */
  public <T> List<T> findList(String db, String collection, Class<T> clz,
      Map<String, Object> params, Long page, Long pageSize, String sortWith) {
    Query query = new Query();
    for (Entry<String, Object> set : params.entrySet()) {
      query.addCriteria(analyzeQueryParam(set.getKey(), set.getValue()));
    }
    if (page > 0 && pageSize > 0) {
      query.skip((page - 1) * pageSize).limit(pageSize.intValue());
    }
    if (StringUtils.isNotEmpty(sortWith)) {
      query.with(Sort.by(Sort.Order.desc(sortWith)));
    }
    if (StringUtils.isEmpty(collection)) {
      return mongoManager.getTemplate(db).find(query, clz);
    } else {
      return mongoManager.getTemplate(db).find(query, clz, collection);
    }
  }

  /**
   * 根据键值对查找对象总数
   *
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> long findCount(Class<T> clz, Map<String, Object> params) {
    return findCount(null, null, clz, params);
  }

  /**
   * 根据键值对查找对象总数
   *
   * @param db     库名
   * @param clz    对象的Class
   * @param params 参数
   * @return 查出来的对象
   */
  public <T> long findCount(String db, Class<T> clz, Map<String, Object> params) {
    return findCount(db, null, clz, params);
  }

  /**
   * 根据键值对查找对象总数
   *
   * @param db         库名
   * @param collection 集合名
   * @param clz        对象的Class
   * @param params     参数
   * @return 查出来的对象
   */
  public <T> long findCount(String db, String collection, Class<T> clz,
      Map<String, Object> params) {
    Query query = new Query();
    for (Entry<String, Object> set : params.entrySet()) {
      query.addCriteria(analyzeQueryParam(set.getKey(), set.getValue()));
    }
    if (StringUtils.isEmpty(collection)) {
      return mongoManager.getTemplate(db).count(query, clz);
    } else {
      return mongoManager.getTemplate(db).count(query, clz, collection);
    }
  }

  private Criteria analyzeQueryParam(String key, Object value) {
    String gte = ">=";
    String gt = ">";
    String lte = "<=";
    String lt = "<";
    String between = "#between";
    if (key.contains(between)) {
      String[] values = StrUtil.split(((String)value), "#");
      long start = Long.parseLong(values[0]);
      long end = Long.parseLong(values[1]);
      key = key.replaceAll(between, "");
      log.debug("analyzeQueryParam between======{},{},{}", key, start, end);
      return Criteria.where(key).gte(start).lt(end);
    } else if (key.contains(gte)) {
      return Criteria.where(key.replaceAll(gte, "")).gte(value);
    } else if (key.contains(gt)) {
      return Criteria.where(key.replaceAll(gt, "")).gt(value);
    } else if (key.contains(lte)) {
      return Criteria.where(key.replaceAll(lte, "")).lte(value);
    } else if (key.contains(lt)) {
      return Criteria.where(key.replaceAll(lt, "")).lt(value);
    } else {
      return Criteria.where(key).is(value);
    }
  }

  /**
   * 插入实体类
   *
   * @param po 实体类
   * @return 结果
   */
  public <T> T insert(T po) {
    return insert(null, null, po);
  }

  /**
   * 插入实体类
   *
   * @param db 库名
   * @param po 实体类
   * @return 结果
   */
  public <T> T insert(String db, T po) {
    return insert(db, null, po);
  }

  /**
   * 插入实体类
   *
   * @param db         库名
   * @param collection 集合名
   * @param po         实体类
   * @return 结果
   */
  public <T> T insert(String db, String collection, T po) {
    if (StringUtils.isEmpty(collection)) {
      return mongoManager.getTemplate(db).insert(po);
    } else {
      return mongoManager.getTemplate(db).insert(po, collection);
    }
  }

  /**
   * 保存实体类
   *
   * @param po 实体类
   * @return 结果
   */
  public <T> T save(T po) {
    return save(null, null, po);
  }

  /**
   * 保存实体类
   *
   * @param db 库名
   * @param po 实体类
   * @return 结果
   */
  public <T> T save(String db, T po) {
    return save(db, null, po);
  }

  /**
   * 保存实体类
   *
   * @param db         库名
   * @param collection 集合名
   * @param po         实体类
   * @return 结果
   */
  public <T> T save(String db, String collection, T po) {
    if (StringUtils.isEmpty(collection)) {
      return mongoManager.getTemplate(db).save(po);
    } else {
      return mongoManager.getTemplate(db).save(po, collection);
    }

  }

  /**
   * 移除实体类
   *
   * @param po 实体类
   */
  public <T> void remove(T po) {
    remove(null, po);
  }

  /**
   * 移除实体类
   *
   * @param db 库名
   * @param po 实体类
   */
  public <T> void remove(String db, T po) {
    mongoManager.getTemplate(db).remove(po);
  }

  /**
   * 移除实体类
   *
   * @param db         库名
   * @param collection 集合名
   * @param po         实体类
   */
  public <T> void remove(String db, String collection, T po) {
    mongoManager.getTemplate(db).remove(po, collection);
  }

  /**
   * 根据数据库名查找集合列表
   *
   * @param db  库名
   * @return 查出来的集合列表
   */
  public Set<String> findCollections(String db) {
    return mongoManager.getTemplate(db).getCollectionNames();
  }

  /**
   * 根据数据库名和集合名删除集合
   *
   * @param db              库名
   * @param collectionName  集合名
   */
  public void dropCollection(String db, String collectionName) {
    mongoManager.getTemplate(db).dropCollection(collectionName);
  }

  public void createIndex(String db, String collection, String... fieldNames) {
    try {
      IndexOperations indexOperations = mongoManager.getTemplate(db).indexOps(collection);
      Index index = new Index().background();
      for (String fieldName : fieldNames) {
        index.on(fieldName, Direction.ASC);
      }
      indexOperations.ensureIndex(index);
    } catch (Exception ignored) {
    }
  }

  public void createExpireIndex(String db, String collection, long expireTime, String... fieldNames) {
    try {
      IndexOperations indexOperations = mongoManager.getTemplate(db).indexOps(collection);
      Index index = new Index().expire(expireTime).background();
      for (String fieldName : fieldNames) {
        index.on(fieldName, Direction.ASC);
      }
      indexOperations.ensureIndex(index);
    } catch (Exception ignored) {
    }
  }
}
